package sharemem

import (
	"encoding/json"
	"math"
	"server_cluster/common/gdconf"
	"server_cluster/common/logger"
	"server_cluster/common/protoc/pb"
	"server_cluster/http/pkg/random"
)

const (
	GachaGuaranteePurpleTimes = 10
	GachaHistoryListMax       = 500
)

type GachaHistory struct {
	ItemId    int32
	Timestamp int64
}

func (g *GachaHistory) IsUse() bool {
	return g.ItemId != 0
}

type GachaPool struct {
	ScheduleId       int32                             // 卡池排期id
	GachaType        int32                             // 卡池类型
	TotalGachaCount  int32                             // 总抽卡次数
	WishItemIdList   [gdconf.WishItemIdListMax]int32   // 许愿道具id列表
	GachaHistoryList [GachaHistoryListMax]GachaHistory // 历史记录列表
	IsWishTriggered  int32                             // 是否触发过许愿
}

func (g *GachaPool) MarshalJSON() ([]byte, error) {
	dbGachaPool := struct {
		ScheduleId       int32          // 卡池排期id
		GachaType        int32          // 卡池类型
		TotalGachaCount  int32          // 总抽卡次数
		WishItemIdList   []int32        // 许愿道具id列表
		GachaHistoryList []GachaHistory // 历史记录列表
	}{}
	dbGachaPool.ScheduleId = g.ScheduleId
	dbGachaPool.GachaType = g.GachaType
	dbGachaPool.TotalGachaCount = g.TotalGachaCount
	for _, wishItemId := range g.WishItemIdList {
		if wishItemId == 0 {
			continue
		}
		dbGachaPool.WishItemIdList = append(dbGachaPool.WishItemIdList, wishItemId)
	}
	for _, gachaHistory := range g.GachaHistoryList {
		if !gachaHistory.IsUse() {
			continue
		}
		dbGachaPool.GachaHistoryList = append(dbGachaPool.GachaHistoryList, gachaHistory)
	}
	data, err := json.Marshal(dbGachaPool)
	if err != nil {
		return nil, err
	}
	return data, nil
}

func (g *GachaPool) UnmarshalJSON(data []byte) error {
	dbGachaPool := struct {
		ScheduleId       int32          // 卡池排期id
		GachaType        int32          // 卡池类型
		TotalGachaCount  int32          // 总抽卡次数
		WishItemIdList   []int32        // 许愿道具id列表
		GachaHistoryList []GachaHistory // 历史记录列表
	}{}
	err := json.Unmarshal(data, &dbGachaPool)
	if err != nil {
		return err
	}
	g.ScheduleId = dbGachaPool.ScheduleId
	g.GachaType = dbGachaPool.GachaType
	g.TotalGachaCount = dbGachaPool.TotalGachaCount
	for index, wishItemId := range dbGachaPool.WishItemIdList {
		g.WishItemIdList[index] = wishItemId
	}
	for index, gachaHistory := range dbGachaPool.GachaHistoryList {
		g.GachaHistoryList[index] = gachaHistory
	}
	return nil
}

func (g *GachaPool) IsUse() bool {
	return g.ScheduleId != 0
}

func (g *GachaPool) GetGachaHistoryList() []*GachaHistory {
	list := make([]*GachaHistory, 0)
	for i := len(g.GachaHistoryList) - 1; i >= 0; i-- {
		if g.GachaHistoryList[i].ItemId == 0 {
			continue
		}
		list = append(list, &g.GachaHistoryList[i])
	}
	return list
}

func (g *GachaPool) AddGachaHistory(itemId int32) {
	isFull := true
	for i := 0; i < len(g.GachaHistoryList); i++ {
		if g.GachaHistoryList[i].ItemId == 0 {
			isFull = false
			break
		}
	}
	if isFull {
		for i := 0; i < len(g.GachaHistoryList); i++ {
			if i == len(g.GachaHistoryList)-1 {
				g.GachaHistoryList[i].ItemId = 0
				g.GachaHistoryList[i].Timestamp = 0
			} else {
				g.GachaHistoryList[i].ItemId = g.GachaHistoryList[i+1].ItemId
				g.GachaHistoryList[i].Timestamp = g.GachaHistoryList[i+1].Timestamp
			}
		}
	}
	for i := 0; i < len(g.GachaHistoryList); i++ {
		if g.GachaHistoryList[i].ItemId == 0 {
			g.GachaHistoryList[i].ItemId = itemId
			g.GachaHistoryList[i].Timestamp = MyShm.TimeNow
			return
		}
	}
}

func (g *GachaPool) GetWishItemIdList() []int32 {
	list := make([]int32, 0)
	for i := 0; i < len(g.WishItemIdList); i++ {
		if g.WishItemIdList[i] == 0 {
			break
		}
		list = append(list, g.WishItemIdList[i])
	}
	return list
}

func (g *GachaPool) SetWishItemIdList(list []int32) {
	if len(list) > len(g.WishItemIdList) {
		logger.Error("wish item id list too long, list: %v", list)
		return
	}
	for i := 0; i < len(g.WishItemIdList); i++ {
		g.WishItemIdList[i] = 0
	}
	for i := 0; i < len(list); i++ {
		g.WishItemIdList[i] = list[i]
	}
}

func (g *GachaPool) MemInit() {
	g.ScheduleId = 0
	g.GachaType = 0
	g.TotalGachaCount = 0
	for i := 0; i < len(g.WishItemIdList); i++ {
		g.WishItemIdList[i] = 0
	}
	for i := 0; i < len(g.GachaHistoryList); i++ {
		g.GachaHistoryList[i].ItemId = 0
		g.GachaHistoryList[i].Timestamp = 0
	}
}

func (g *GachaPool) InitGachaPool(scheduleId int32) {
	gachaConfig := gdconf.GetConfigGacha(scheduleId)
	if gachaConfig == nil {
		logger.Error("get gacha config is nil, id: %v", scheduleId)
		return
	}
	g.ScheduleId = scheduleId
	g.GachaType = gachaConfig.GachaType
	g.TotalGachaCount = 0
}

type GachaGuarantee struct {
	LastSuperOrangeTimes int32 // 究极保底计数
	LastOrangeTimes      int32 // 五星保底计数
	LastPurpleTimes      int32 // 四星保底计数
}

type UserGacha struct {
	GachaPoolList      [50]GachaPool      // 卡池列表
	GachaGuaranteeList [10]GachaGuarantee // 卡池类型保底列表 直接用类型索引 每个元素都不为空
}

func (g *UserGacha) MarshalJSON() ([]byte, error) {
	dbUserGacha := struct {
		GachaPoolList      []GachaPool        // 卡池列表
		GachaGuaranteeList [10]GachaGuarantee // 卡池类型保底列表 直接用类型索引 每个元素都不为空
	}{}
	for _, gachaPool := range g.GachaPoolList {
		if !gachaPool.IsUse() {
			continue
		}
		dbUserGacha.GachaPoolList = append(dbUserGacha.GachaPoolList, gachaPool)
	}
	for index, gachaGuarantee := range g.GachaGuaranteeList {
		dbUserGacha.GachaGuaranteeList[index] = gachaGuarantee
	}
	data, err := json.Marshal(dbUserGacha)
	if err != nil {
		return nil, err
	}
	return data, nil
}

func (g *UserGacha) UnmarshalJSON(data []byte) error {
	dbUserGacha := struct {
		GachaPoolList      []GachaPool        // 卡池列表
		GachaGuaranteeList [10]GachaGuarantee // 卡池类型保底列表 直接用类型索引 每个元素都不为空
	}{}
	err := json.Unmarshal(data, &dbUserGacha)
	if err != nil {
		return err
	}
	for index, gachaPool := range dbUserGacha.GachaPoolList {
		g.GachaPoolList[index] = gachaPool
	}
	for index, gachaGuarantee := range dbUserGacha.GachaGuaranteeList {
		g.GachaGuaranteeList[index] = gachaGuarantee
	}
	return nil
}

func (g *UserGacha) MemInit() {
	for i := 0; i < len(g.GachaPoolList); i++ {
		g.GachaPoolList[i].MemInit()
	}
	for i := 0; i < len(g.GachaGuaranteeList); i++ {
		g.GachaGuaranteeList[i].LastOrangeTimes = 0
		g.GachaGuaranteeList[i].LastPurpleTimes = 0
		g.GachaGuaranteeList[i].LastSuperOrangeTimes = 0
	}
}

func (g *UserGacha) GetGachaPoolList() []*GachaPool {
	list := make([]*GachaPool, 0)
	for i := 0; i < len(g.GachaPoolList); i++ {
		if g.GachaPoolList[i].ScheduleId == 0 {
			break
		}
		list = append(list, &g.GachaPoolList[i])
	}
	return list
}

func (g *UserGacha) GetGachaPool(scheduleId int32) *GachaPool {
	for i := 0; i < len(g.GachaPoolList); i++ {
		gachaPool := &(g.GachaPoolList[i])
		if gachaPool.ScheduleId == scheduleId {
			return gachaPool
		}
	}
	for i := 0; i < len(g.GachaPoolList); i++ {
		gachaPool := &(g.GachaPoolList[i])
		if gachaPool.ScheduleId == 0 {
			// 获取时不存在自动创建
			gachaPool.InitGachaPool(scheduleId)
			return gachaPool
		}
	}
	return nil
}

func (u *User) DoGacha(scheduleId int32, isTest bool) (bool, int32) {
	gachaConfig := gdconf.GetConfigGacha(scheduleId)
	if gachaConfig == nil {
		logger.Error("get gacha config is nil, id: %v, uid: %v", scheduleId, u.GetUid())
		return false, 0
	}
	gachaPool := u.UserGacha.GetGachaPool(scheduleId)
	gachaPool.TotalGachaCount++
	gachaGuarantee := &(u.UserGacha.GachaGuaranteeList[gachaConfig.GachaType])
	gachaGuarantee.LastOrangeTimes++
	gachaGuarantee.LastPurpleTimes++

	var itemId int32 = 0
	if (gachaPool.TotalGachaCount != 0 && gachaConfig.Front != 0) && gachaPool.TotalGachaCount <= gachaConfig.Front {
		var gachaProbConfig = gdconf.GetConfigGachaProb(gachaConfig.FrontGroup, gachaPool.TotalGachaCount)
		guaranteeDrop := u.DoGachaRandDropOnce(gachaProbConfig.NormalPropId)
		if guaranteeDrop == nil {
			logger.Error("gacha rws random error, poolId: %v, uid: %v", gachaProbConfig.NormalPropId, u.GetUid())
			return false, 0
		}
		itemId = guaranteeDrop.ItemId
	} else {
		var propId int32 = 0
		switch gachaConfig.ResetType {
		case 2:
			gachaGuarantee.LastSuperOrangeTimes++
			if gachaGuarantee.LastSuperOrangeTimes >= gachaConfig.RequiredUPNum {
				gachaGuarantee.LastOrangeTimes = 0
				gachaGuarantee.LastSuperOrangeTimes = 0
				itemId = gachaConfig.RequiredUpId
			} else {
				if gachaGuarantee.LastOrangeTimes >= gachaConfig.RequiredSSRNum {
					gachaGuarantee.LastOrangeTimes = 0
					propId = gachaConfig.RequiredSSRId
				} else {
					drop := u.DoGachaRandDropOnce(gachaConfig.OrdinaryGroup)
					if drop == nil {
						logger.Error("gacha rws random error, poolId: %v, uid: %v", gachaConfig.OrdinaryGroup, u.GetUid())
						return false, 0
					}
					itemId = drop.ItemId
					itemConfig := gdconf.GetItemConfig(itemId)
					if itemConfig == nil {
						return false, 0
					}

					switch itemConfig.Rarity {
					case gdconf.Orange:
						if gachaConfig.RequiredUpId != 0 && gachaConfig.RequiredUpId == itemId {
							gachaGuarantee.LastSuperOrangeTimes = 0
						}
						gachaGuarantee.LastOrangeTimes = 0
					case gdconf.Purple:
						gachaGuarantee.LastPurpleTimes = 0
					default:
						// 四星保底
						if gachaGuarantee.LastPurpleTimes >= gachaConfig.RequiredSRNum {
							gachaGuarantee.LastPurpleTimes = 0
							guaranteeDrop := u.DoGachaRandDropOnce(gachaConfig.RequiredSRId)
							if guaranteeDrop == nil {
								logger.Error("gacha rws random error, poolId: %v, uid: %v", gachaConfig.RequiredSRId, u.GetUid())
								return false, 0
							}
							itemId = guaranteeDrop.ItemId

							tmpItemConfig := gdconf.GetItemConfig(itemId)
							if tmpItemConfig == nil {
								return false, 0
							}
							if tmpItemConfig.Rarity == gdconf.Purple {
								gachaGuarantee.LastPurpleTimes = 0
							} else if tmpItemConfig.Rarity == gdconf.Orange {
								if gachaConfig.RequiredUpId != 0 && gachaConfig.RequiredUpId == itemId {
									gachaGuarantee.LastSuperOrangeTimes = 0
								}
								gachaGuarantee.LastOrangeTimes = 0
							}
						}
					}
				}
				if itemId == 0 && propId == 0 {
					logger.Error("drop fail itemID = 0,propID = 0;poolId: %v,dropCount1: %v,dropCount2: %v,dropCount3: %v",
						scheduleId, gachaGuarantee.LastPurpleTimes, gachaGuarantee.LastOrangeTimes, gachaGuarantee.LastSuperOrangeTimes)
				}
				if itemId == 0 {
					guaranteeDrop := u.DoGachaRandDropOnce(propId)
					if guaranteeDrop == nil {
						logger.Error("gacha rws random error, poolId: %v, uid: %v", propId, u.GetUid())
						return false, 0
					}
					itemId = guaranteeDrop.ItemId
				}
			}
		default:
			if gachaGuarantee.LastOrangeTimes >= gachaConfig.RequiredSSRNum {
				gachaGuarantee.LastOrangeTimes = 0
				propId = gachaConfig.RequiredSSRId
			} else {
				drop := u.DoGachaRandDropOnce(gachaConfig.OrdinaryGroup)
				if drop == nil {
					logger.Error("gacha rws random error, poolId: %v, uid: %v", gachaConfig.OrdinaryGroup, u.GetUid())
					return false, 0
				}
				itemId = drop.ItemId
				itemConfig := gdconf.GetItemConfig(itemId)
				if itemConfig == nil {
					return false, 0
				}

				switch itemConfig.Rarity {
				case gdconf.Orange:
					if gachaConfig.RequiredUpId != 0 && gachaConfig.RequiredUpId == itemId {
						gachaGuarantee.LastSuperOrangeTimes = 0
					}
					gachaGuarantee.LastOrangeTimes = 0
				case gdconf.Purple:
					gachaGuarantee.LastPurpleTimes = 0
				default:
					// 四星保底
					if gachaGuarantee.LastPurpleTimes >= gachaConfig.RequiredSRNum {
						gachaGuarantee.LastPurpleTimes = 0
						guaranteeDrop := u.DoGachaRandDropOnce(gachaConfig.RequiredSRId)
						if guaranteeDrop == nil {
							logger.Error("gacha rws random error, poolId: %v, uid: %v", gachaConfig.RequiredSRId, u.GetUid())
							return false, 0
						}
						itemId = guaranteeDrop.ItemId

						tmpItemConfig := gdconf.GetItemConfig(itemId)
						if tmpItemConfig == nil {
							return false, 0
						}
						if tmpItemConfig.Rarity == gdconf.Purple {
							gachaGuarantee.LastPurpleTimes = 0
						} else if tmpItemConfig.Rarity == gdconf.Orange {
							if gachaConfig.RequiredUpId != 0 && gachaConfig.RequiredUpId == itemId {
								gachaGuarantee.LastSuperOrangeTimes = 0
							}
							gachaGuarantee.LastOrangeTimes = 0
						}
					}
				}
			}
			if itemId == 0 && propId == 0 {
				logger.Error("drop fail itemID = 0,propID = 0;poolId: %v,dropCount1: %v,dropCount2: %v,dropCount3: %v",
					scheduleId, gachaGuarantee.LastPurpleTimes, gachaGuarantee.LastOrangeTimes, gachaGuarantee.LastSuperOrangeTimes)
			}
			if itemId == 0 {
				guaranteeDrop := u.DoGachaRandDropOnce(propId)
				if guaranteeDrop == nil {
					logger.Error("gacha rws random error, poolId: %v, uid: %v", propId, u.GetUid())
					return false, 0
				}
				itemId = guaranteeDrop.ItemId
			}
		}
	}
	// 许愿替换五星
	if gachaPool.IsWishTriggered == 0 {
		itemConfig := gdconf.GetItemConfig(itemId)
		if itemConfig == nil {
			return false, 0
		}
		wishItemIdList := gachaPool.GetWishItemIdList()
		if itemConfig.Rarity == gdconf.Orange && len(wishItemIdList) != 0 {
			itemIndex := random.GetRandomInt32(0, int32(len(wishItemIdList)-1))
			gachaPool.IsWishTriggered = 1
			itemId = wishItemIdList[itemIndex]
		}
	}
	if !isTest {
		gachaPool.AddGachaHistory(itemId)
		u.TriggerActivityTask(gdconf.ActivityTaskFinishCondGacha, scheduleId)
	}
	return true, itemId
}

func (u *User) DoGachaRandDropOnce(prob2Id int32) *gdconf.GachaPoolConfig {
	gachaProb2Config := gdconf.GetConfigGachaProb2ByProb2Id(prob2Id)
	if gachaProb2Config == nil {
		logger.Error("gachaProb2Config is nil, prob2Id: %v", prob2Id)
		return nil
	}
	gachaPoolWeight := RWSRandom[*gdconf.GachaPoolWeight](gachaProb2Config.GachaPoolWeightList)
	if gachaPoolWeight == nil {
		logger.Error("gachaPoolWeight is nil, list: %v", gachaProb2Config.GachaPoolWeightList)
		return nil
	}
	gachaPoolListConfig := gdconf.GetConfigGachaPoolByPoolId(gachaPoolWeight.GachaPoolId)
	gachaPoolItem := RWSRandom[*gdconf.GachaPoolConfig](gachaPoolListConfig)
	if gachaPoolItem == nil {
		logger.Error("gachaPoolItem is nil, list: %v", gachaPoolListConfig)
		return nil
	}
	return gachaPoolItem
}

type RWSWeightItem interface {
	GetWeight() int32
}

func RWSRandom[T RWSWeightItem](list []T) T {
	weightAll := int32(0)
	for _, drop := range list {
		weightAll += drop.GetWeight()
	}
	randNum := random.GetRandomInt32(0, weightAll-1)
	sumWeight := int32(0)
	for _, drop := range list {
		sumWeight += drop.GetWeight()
		if sumWeight > randNum {
			return drop
		}
	}
	var t T
	return t
}

func (u *User) PacketGachaInfoList() []*pb.GachaInfo {
	gachaInfoList := make([]*pb.GachaInfo, 0)
	for id := range gdconf.GetConfigGachaMap() {
		gachaInfo := u.PacketGachaInfo(id)
		if gachaInfo == nil {
			continue
		}
		if gachaInfo.BeginTime != 0 && MyShm.TimeNow < gachaInfo.BeginTime || gachaInfo.EndTime != 0 && MyShm.TimeNow > gachaInfo.EndTime {
			continue
		}
		if gachaInfo.GachaTimesLimit != 0 && gachaInfo.LeftGachaTimes == 0 {
			continue
		}
		gachaInfoList = append(gachaInfoList, gachaInfo)
	}
	return gachaInfoList
}

func (u *User) PacketGachaInfo(scheduleId int32) *pb.GachaInfo {
	gachaConfig := gdconf.GetConfigGacha(scheduleId)
	if gachaConfig == nil {
		logger.Error("get gacha config is nil, id: %v", scheduleId)
		return nil
	}
	gachaPool := u.UserGacha.GetGachaPool(scheduleId)
	tenCostItemNum := int32(10)
	if gachaConfig.TenCostItemCount != 0 && gachaPool.TotalGachaCount < (gachaConfig.TenCostItemCountNum*10) {
		tenCostItemNum = gachaConfig.TenCostItemCount
	}
	gachaGuarantee := &(u.UserGacha.GachaGuaranteeList[gachaConfig.GachaType])

	var beginTime, endTime int64
	if gachaConfig.BeginTime != nil {
		beginTime = gachaConfig.BeginTime.Unix()
		endTime = gachaConfig.EndTime.Unix()
	}

	return &pb.GachaInfo{
		ScheduleId:        gachaConfig.Id,
		GachaType:         gachaConfig.GachaType,
		BeginTime:         beginTime,
		EndTime:           endTime,
		LeftGachaTimes:    int32(math.Max(0, float64(gachaConfig.GachaTimesLimit-gachaPool.TotalGachaCount))),
		GachaTimesLimit:   gachaConfig.GachaTimesLimit,
		CostItemId:        gachaConfig.CostItem,
		CostItemNum:       1,
		TenCostItemId:     gachaConfig.CostItem,
		TenCostItemNum:    tenCostItemNum,
		SsrGuarantee:      int32(math.Max(1, float64(gachaConfig.RequiredSSRNum-gachaGuarantee.LastOrangeTimes))),
		SrGuarantee:       int32(math.Max(1, float64(gachaConfig.RequiredSRNum-gachaGuarantee.LastPurpleTimes))),
		SsrSuperGuarantee: int32(math.Max(1, float64(gachaConfig.RequiredUPNum-gachaGuarantee.LastSuperOrangeTimes))),
		WishItemIdList:    gachaPool.GetWishItemIdList(),
	}
}
